/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.mapper;

import java.util.ArrayList;
import java.util.List;

import org.apache.catalina.Container;
import org.apache.catalina.ContainerEvent;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Service;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.Wrapper;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;


/**
 * Mapper listener.
 * MapperListener实现了ContainerListener和LifecycleListener接口，用于在容器组件状态发生变更时，注册或取消对应容器的映射关系，
 * 这么做主要是为了支持Tomcat的热部署功能
 * @author Remy Maucherat
 * @author Costin Manolache
 */
public class MapperListener extends LifecycleMBeanBase
        implements ContainerListener, LifecycleListener {


    private static final Log log = LogFactory.getLog(MapperListener.class);


    // ----------------------------------------------------- Instance Variables
    /**
     * Associated mapper.
     * 该属性比较重要，维护了请求和容器的映射关系
     */
    private final Mapper mapper;

    /**
     * Associated service
     */
    private final Service service;


    /**
     * The string manager for this package.
     */
    private static final StringManager sm =
        StringManager.getManager(Constants.Package);

    /**
     * The domain (effectively the engine) this mapper is associated with
     */
    private final String domain = null;


    // ----------------------------------------------------------- Constructors

    /**
     * Create mapper listener.
     *
     * @param service The service this listener is associated with
     */
    public MapperListener(Service service) {
        this.service = service;
        //返回的是 StandardService 对象的 mapper 成员变量
        this.mapper = service.getMapper();
    }


    // ------------------------------------------------------- Lifecycle Methods

    @Override
    public void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);
        // 1. engine容器不存在，则MapperListener也不需要启动(其内部mapper维护的就是容器跟请求路径的映射关系)
        Engine engine = service.getContainer();
        if (engine == null) {
            return;
        }
        // 2. 查找默认主机，并设置到mapper(service层级的)的defaultHostName属性中
        findDefaultHost();
        // 3. 对容器及下面的所有子容器添加事件监听器
        // MapperListener本身实现了LifecycleLietener和containerListener监听器 当容器组件发生变化时会触发该监听器同步mapper集合
        addListeners(engine);
        // 4. 注册engine下面的host、context和wrapper，registerHost会注册host及下面的子容器
        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // Registering the host will register the context and wrappers
                //注册Engine的子容器Host,context,wrappers
                registerHost(host);
            }
        }
    }


    @Override
    public void stopInternal() throws LifecycleException {
        setState(LifecycleState.STOPPING);

        Engine engine = service.getContainer();
        if (engine == null) {
            return;
        }
        removeListeners(engine);
    }


    @Override
    protected String getDomainInternal() {
        if (service instanceof LifecycleMBeanBase) {
            return service.getDomain();
        } else {
            return null;
        }
    }


    @Override
    protected String getObjectNameKeyProperties() {
        // Same as connector but Mapper rather than Connector
        return "type=Mapper";
    }

    // --------------------------------------------- Container Listener methods

    @Override
    public void containerEvent(ContainerEvent event) {
        //组件发布addChild会触发该逻辑
        if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
            Container child = (Container) event.getData();
            //为新的组件绑定MapperListener监听器
            addListeners(child);
            // If child is started then it is too late for life-cycle listener
            // to register the child so register it here
            // 根据组件的类型选择不同的注册方式  前提是该组件必须是可用状态（start）
            if (child.getState().isAvailable()) {
                if (child instanceof Host) {
                    //host注册
                    registerHost((Host) child);
                } else if (child instanceof Context) {
                    //context注册
                    registerContext((Context) child);
                } else if (child instanceof Wrapper) {
                    // Only if the Context has started. If it has not, then it
                    // will have its own "after_start" life-cycle event later.
                    if (child.getParent().getState().isAvailable()) {
                        //Wrapper注册
                        registerWrapper((Wrapper) child);
                    }
                }
            }
        //removeChild事件时将移除的child的监听器移除，无需取消注册，其停止的时候会取消注册
        } else if (Container.REMOVE_CHILD_EVENT.equals(event.getType())) {
            Container child = (Container) event.getData();
            removeListeners(child);
            // No need to unregister - life-cycle listener will handle this when
            // the child stops
        //host组件增加或者移除别名
        } else if (Host.ADD_ALIAS_EVENT.equals(event.getType())) {
            // Handle dynamically adding host aliases
            mapper.addHostAlias(((Host) event.getSource()).getName(),
                    event.getData().toString());
        } else if (Host.REMOVE_ALIAS_EVENT.equals(event.getType())) {
            // Handle dynamically removing host aliases
            mapper.removeHostAlias(event.getData().toString());
        //发布addMapping事件 该mapping(url-partten)以及发布该事件的wrapper会被封装成mappedWrapper 添加到ContextVersion的属性集合中
        } else if (Wrapper.ADD_MAPPING_EVENT.equals(event.getType())) {
            // Handle dynamically adding wrappers
            Wrapper wrapper = (Wrapper) event.getSource();
            Context context = (Context) wrapper.getParent();
            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }
            String version = context.getWebappVersion();
            String hostName = context.getParent().getName();
            String wrapperName = wrapper.getName();
            String mapping = (String) event.getData();
            boolean jspWildCard = ("jsp".equals(wrapperName)
                    && mapping.endsWith("/*"));
            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
                    jspWildCard, context.isResourceOnlyServlet(wrapperName));
        //移除mapping
        } else if (Wrapper.REMOVE_MAPPING_EVENT.equals(event.getType())) {
            // Handle dynamically removing wrappers
            Wrapper wrapper = (Wrapper) event.getSource();

            Context context = (Context) wrapper.getParent();
            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }
            String version = context.getWebappVersion();
            String hostName = context.getParent().getName();

            String mapping = (String) event.getData();

            mapper.removeWrapper(hostName, contextPath, version, mapping);
        //context增加欢迎页，找到对应的contextVersion，将新添加的欢迎页放入到其welcomeResources属性中
        } else if (Context.ADD_WELCOME_FILE_EVENT.equals(event.getType())) {
            // Handle dynamically adding welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            String welcomeFile = (String) event.getData();

            mapper.addWelcomeFile(hostName, contextPath,
                    context.getWebappVersion(), welcomeFile);
        //context删除某个欢迎页
        } else if (Context.REMOVE_WELCOME_FILE_EVENT.equals(event.getType())) {
            // Handle dynamically removing welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            String welcomeFile = (String) event.getData();

            mapper.removeWelcomeFile(hostName, contextPath,
                    context.getWebappVersion(), welcomeFile);
        //context清除欢迎页
        } else if (Context.CLEAR_WELCOME_FILES_EVENT.equals(event.getType())) {
            // Handle dynamically clearing welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            mapper.clearWelcomeFiles(hostName, contextPath,
                    context.getWebappVersion());
        }
    }


    // ------------------------------------------------------ Protected Methods
    /*
     * 1、用户在浏览器中输入网址localhost:8080/test/index.jsp，请求被发送到本机端口8080，被在那里监听的Coyote HTTP/1.1 Connector获得；
     * 2、Connector把该请求交给它所在的Service的Engine（Container）来处理，并等待Engine的回应；
     * 3、Engine获得请求localhost/test/index.jsp，匹配所有的虚拟主机Host；
     * 4、Engine匹配到名为localhost的Host（即使匹配不到也把请求交给该Host处理，因为该Host被定义为该Engine的默认主机），名为localhost的Host获得请求/test/index.jsp，匹配它所拥有的所有Context。Host匹配到路径为/test的Context（如果匹配不到就把该请求交给路径名为“ ”的Context去处理）；
     * 5、path=“/test”的Context获得请求/index.jsp，在它的mapping table中寻找出对应的Servlet(这地方就是用到了上面的Mapper去找到对应的Servlet对象)。Context匹配到URL Pattern为*、jsp的Servlet，对应于JspServlet类；
     * 6、构造HttpServletRequest对象和HttpServletResponse对象，作为参数调用JspServlet的doGet()或doPost(),执行业务逻辑、数据存储等；
     * 7、Context把执行完之后的HttpServletResponse对象返回给Host；
     * 8、Host把HttpServletResponse对象返回给Engine；
     * 9、Engine把HttpServletResponse对象返回Connector；
     * 10、Connector把HttpServletResponse对象返回给客户Browser。
    * */
    private void findDefaultHost() {
        // Engine容器会从HTTP Connector接收HTTP请求，并且会根据主机名/IP地址交由相应的主机(host虚拟主机)处理请求。 但是如果匹配不到会交由默认主机处理
        Engine engine = service.getContainer();
        //<engin>标签中的defaultHost属性信息 默认为localhost
        String defaultHost = engine.getDefaultHost();

        boolean found = false;
        // 如果defaultHost属性不为空，则查找hosts下面的所有主机名及别名。
        // 1. 找到了则设置到mapper的defaultHostName属性
        // 2. 没找到则记录警告信息
        if (defaultHost != null && defaultHost.length() > 0) {
            //获取engine下的所有虚拟主机host
            Container[] containers = engine.findChildren();

            for (Container container : containers) {
                Host host = (Host) container;
                //如果集合中存在默认主机名则直接跳出 否则查找别名中是否存在
                if (defaultHost.equalsIgnoreCase(host.getName())) {
                    found = true;
                    break;
                }

                String[] aliases = host.findAliases();
                for (String alias : aliases) {
                    if (defaultHost.equalsIgnoreCase(alias)) {
                        found = true;
                        break;
                    }
                }
            }
        }
        //如果存在则将默认主机设置到mapper中
        if (found) {
            mapper.setDefaultHostName(defaultHost);
        } else {
            log.error(sm.getString("mapperListener.unknownDefaultHost", defaultHost, service));
        }
    }


    /**
     * Register host.
     * registerHost方法先调用mapper.addHost，然后调用registerContext方法注册Host的子容器Context。
     * mapper.addHost方法是将Host加入的Mapper类的的成员变量MappedHost[] hosts 中。
     */
    private void registerHost(Host host) {

        String[] aliases = host.findAliases();
        // 往mapper中添加主机
        mapper.addHost(host.getName(), aliases, host);
        // 注册host下的每个context  test#test1##1 test#test1##2 同一个项目的两个版本，会按照两个context来进行解析
        for (Container container : host.findChildren()) {
            //通过available标志为true来确定当前context处在启动中
            if (container.getState().isAvailable()) {
                registerContext((Context) container);
            }
        }

        // Default host may have changed
        findDefaultHost();

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerHost",
                    host.getName(), domain, service));
        }
    }


    /**
     * Unregister host.
     * 对host进行取消注册操作，根据hostname来remove
     */
    private void unregisterHost(Host host) {

        String hostname = host.getName();

        mapper.removeHost(hostname);

        // Default host may have changed
        //有可能移除的host组件就是默认的，所以需要重新筛选默认组件
        findDefaultHost();

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.unregisterHost", hostname,
                    domain, service));
        }
    }


    /**
     * Unregister wrapper.
     * 对wrapper取消注册
     */
    private void unregisterWrapper(Wrapper wrapper) {

        Context context = ((Context) wrapper.getParent());
        String contextPath = context.getPath();
        String wrapperName = wrapper.getName();

        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        String version = context.getWebappVersion();
        String hostName = context.getParent().getName();

        String[] mappings = wrapper.findMappings();
        // 一个wrapper可能有多个map地址，对每个地址都需要移除操作，所以这儿是一个循环
        for (String mapping : mappings) {
            mapper.removeWrapper(hostName, contextPath, version,  mapping);
        }

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.unregisterWrapper",
                    wrapperName, contextPath, service));
        }
    }


    /**
     * Register context.
     */
    private void registerContext(Context context) {
        // contextPath如果为斜杠，则统一转换为空字符串
        String contextPath = context.getPath();
        //root项目
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        Host host = (Host)context.getParent();
        //当前context下的所有资源
        WebResourceRoot resources = context.getResources();
        //当前应用的欢迎页信息
        String[] welcomeFiles = context.findWelcomeFiles();
        //每一个<servlert-mapping>标签中的<url-pattern>属性信息都会封装成一个WrapperMappingInfo对象
        List<WrapperMappingInfo> wrappers = new ArrayList<>();
        //获取wrapper下的mapping(<url-pattern> 一个wrapper可以有多个)信息，每个封装成一个WrapperMappingInfo对象，添加到wrappers集合中
        for (Container container : context.findChildren()) {
            // 准备wrapper信息，以便后续插入mapper
            prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

            if(log.isDebugEnabled()) {
                log.debug(sm.getString("mapperListener.registerWrapper",
                        container.getName(), contextPath, service));
            }
        }
        // 将context添加到mapper
        mapper.addContextVersion(host.getName(), host, contextPath,
                context.getWebappVersion(), context, welcomeFiles, resources,
                wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerContext",
                    contextPath, service));
        }
    }


    /**
     * Unregister context.
     * 对context取消注册
     */
    private void unregisterContext(Context context) {

        String contextPath = context.getPath();
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        String hostName = context.getParent().getName();

        if (context.getPaused()) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("mapperListener.pauseContext",
                        contextPath, service));
            }
            // 暂停的context，不能从mapper中移除，只能在mapper暂停
            mapper.pauseContextVersion(context, hostName, contextPath,
                    context.getWebappVersion());
        } else {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("mapperListener.unregisterContext",
                        contextPath, service));
            }
            // 非暂停的context，需要从mapper中移除
            mapper.removeContextVersion(context, hostName, contextPath,
                    context.getWebappVersion());
        }
    }


    /**
     * Register wrapper.
     */
    private void registerWrapper(Wrapper wrapper) {

        Context context = (Context) wrapper.getParent();
        String contextPath = context.getPath();
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        String version = context.getWebappVersion();
        String hostName = context.getParent().getName();

        List<WrapperMappingInfo> wrappers = new ArrayList<>();
        //对新加入的Warpper中的<servlet-mapping>标签进行处理封装到wrappers集合中
        prepareWrapperMappingInfo(context, wrapper, wrappers);
        mapper.addWrappers(hostName, contextPath, version, wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    wrapper.getName(), contextPath, service));
        }
    }

    /*
     * Populate <code>wrappers</code> list with information for registration of
     * mappings for this wrapper in this context.
     */
    private void prepareWrapperMappingInfo(Context context, Wrapper wrapper,
            List<WrapperMappingInfo> wrappers) {
        //servlet对应的名称
        String wrapperName = wrapper.getName();
        //是否资源标记
        boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
        //获取servlet下的所有<servlert-mapping>标签中的<url-pattern>属性信息 即当前wrapper所匹配的路径信息
        String[] mappings = wrapper.findMappings();
        for (String mapping : mappings) {
            //jsp通配符标记
            boolean jspWildCard = (wrapperName.equals("jsp")
                                   && mapping.endsWith("/*"));
            wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard,
                    resourceOnly));
        }
    }

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        //组件发布after_start触发该方法，注册相应的组件
        if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            Object obj = event.getSource();
            if (obj instanceof Wrapper) {
                Wrapper w = (Wrapper) obj;
                // Only if the Context has started. If it has not, then it will
                // have its own "after_start" event later.
                if (w.getParent().getState().isAvailable()) {
                    registerWrapper(w);
                }
            } else if (obj instanceof Context) {
                Context c = (Context) obj;
                // Only if the Host has started. If it has not, then it will
                // have its own "after_start" event later.
                if (c.getParent().getState().isAvailable()) {
                    registerContext(c);
                }
            } else if (obj instanceof Host) {
                registerHost((Host) obj);
            }
        //组件发布before_stop触发该方法，移除相应的组件
        } else if (event.getType().equals(Lifecycle.BEFORE_STOP_EVENT)) {
            Object obj = event.getSource();
            if (obj instanceof Wrapper) {
                unregisterWrapper((Wrapper) obj);
            } else if (obj instanceof Context) {
                //根据context的状态 做了暂停或者移除操作
                unregisterContext((Context) obj);
            } else if (obj instanceof Host) {
                unregisterHost((Host) obj);
            }
        }
    }


    /**
     * Add this mapper to the container and all child containers
     *
     * @param container the container (and any associated children) to which
     *        the mapper is to be added
     */
    private void addListeners(Container container) {
        // 对当前容器添加容器监听器和生命周期监听器，也就是当前对象
        container.addContainerListener(this);
        container.addLifecycleListener(this);
        // 对当前容器下的子容器执行addListeners操作
        for (Container child : container.findChildren()) {
            addListeners(child);
        }
    }


    /**
     * Remove this mapper from the container and all child containers
     *
     * @param container the container (and any associated children) from which
     *        the mapper is to be removed
     */
    private void removeListeners(Container container) {
        container.removeContainerListener(this);
        container.removeLifecycleListener(this);
        for (Container child : container.findChildren()) {
            removeListeners(child);
        }
    }
}
